# Copyright 2007, 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import codecs

import svn.core

import gvn.changebranch
import gvn.cmdline
import gvn.errors
import gvn.util


helptext__gvn_snapshot = """snapshot: Snapshot a changebranch.
usage: snapshot [-c CHANGE]

Without options, snapshot all local changebranches.  Won't snapshot a
changebranch unless necessary; use gvn change to force it.
"""


def CheckOperable(ctx, change_name, change_state):
  """Return {path: change_name} if all paths are under ctx.path_dir.

  Else return empty dict.
  """
  result = {}
  for path in change_state.paths:
    if not gvn.util.IsChild(path, ctx.path_dir):
      gvn.DiagLog('%s, skipping %s\n'
                  % (gvn.errors.ChangeBranchOutsideTarget(path, ctx.path_dir),
                     change_name))
      return {}
    result[path] = change_name
  return result


def MapPathToChangeName(ctx, change_state):
  """Return {path: change_name} for operable changebranches in change_state.

  A changebranch is "operable" if all its paths are under ctx.path_dir.

  Arguments:
  ctx           -- gvn.cmdline.Context
  change_state  -- mapping of change names to gvn.wc.ChangeState objects
  """
  path_to_change_name = {}
  for (change_name, change_state) in change_state.iteritems():
    path_to_change_name.update(CheckOperable(ctx, change_name, change_state))
  return path_to_change_name


def DoIt(ctx, change_state=None, description=None):
  """Snapshot paths in change_state which need it.

  If change_state is None, use ctx.wc.change_state.

  Arguments:
  ctx           -- gvn.cmdline.Context
  change_state  -- optional mapping of change names to gvn.wc.ChangeState
  description   -- optional description
  """
  if change_state is None:
    change_state = ctx.wc.change_state
  path_to_change_name = MapPathToChangeName(ctx, change_state)
  # Get status of paths we'll work on.
  changebranch_status = {}
  iterpool = svn.core.Pool(ctx.pool)
  ctx.wc.Open(path_to_change_name)
  def status_cb(target, status):
    iterpool.clear()
    target = ctx.wc.RelativePath(target)
    try:
      path_status = changebranch_status[path_to_change_name[target]]
    except KeyError:
      path_status = changebranch_status[path_to_change_name[target]] = {}
    path_status[target] = status
  ctx.wc.Status(path_to_change_name,
                recursive=False, get_all=False, no_ignore=False,
                show_children=False, callback=status_cb)
  # Now snapshot each changebranch.
  for change_name in sorted(changebranch_status):
    path_status = changebranch_status[change_name]
    iterpool.clear()
    snap_revision = change_state[change_name].snap_revision
    cb = gvn.changebranch.ChangeBranch(ctx.config, ctx.project, change_name,
                                       snap_revision=snap_revision)
    if description is None:
      this_desc = cb.description
    else:
      this_desc = description
    cb.Snapshot(ctx.wc, path_status, iterpool, description=this_desc,
      # TODO(before-submit): After finished testing, set minimal=False for
      # the next release; that way even old gvn can review the
      # changebranches, it just ignores the state file.  In the release
      # after that, go back to minimal=True unless --full-snapshot.
                minimal=not ctx.options.full_snapshot)
  iterpool.destroy()


def Handle_GvnSnapshot(ctx):
  description = None
  if ctx.options.file is not None:
    description = codecs.open(ctx.options.file,
                              encoding=ctx.encoding).read()
  elif ctx.options.message is not None:
    description = ctx.options.message.decode(ctx.encoding)
  # TODO(epg): force-log

  if ctx.options.change is None:
    # Let DoIt snapshot all changebranches it finds.
    DoIt(ctx)

  else:
    # Call DoIt for just this changebranch.
    (unused_user, change_name, unused_revision) = gvn.util.ParseChangeName(
      ctx.options.change)
    DoIt(ctx, change_state={change_name: ctx.wc.change_state[change_name]},
         description=description)

  return 0

options = gvn.cmdline.AuthOptions(gvn.cmdline.LogOptions([
  'change',
  'full-snapshot',
  'quiet'
]))
gvn.cmdline.AddCommand('snapshot', Handle_GvnSnapshot,
                       helptext__gvn_snapshot, options,
                       {'change': 'changebranch ARG'},
                       aliases=['snap'])
